/**
 * COPYRIGHT (C) 2009. Denny Tran, Omar Jaime, David Deng, Julie Hsieh. All Rights Reserved
 * Class that controls all calendar functions.
 * @author Denny Tran, Omar Jaime, David Deng, Julie Hsieh 
 * @version 1.01 2009-05-05
 */

package data;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class MyCalendar
{
   private ArrayList<Event> events; // list of events

   /**
    * Constructs a new calendar object and initializes its events.
    */
   public MyCalendar()
   {
	   events = new ArrayList<Event>();
   }

   /**
    * Constructs a calendar object with a given event list.
    * @param eventsList The list of events to set this calendar to.
    */
   public MyCalendar(ArrayList<Event> eventsList)
   {
      events = eventsList;
      
      if(events == null)
         events = new ArrayList<Event>();
   }
  
   /**
      Retrieve an event based on name of the event.
      @param eventID name of the event
      @return event object of the corresponding name
      Pre-Condition: eventName is unique.
    */
   public Event getEvent(int eventID)
   {
      for (Event e : events)
         if (eventID == e.getEventID())
            return e;

      return null;
   }

   /**
      Add an event to the Calendar.
      @param e new event
      @return true if added, false if there is a conflict.
    */
   public boolean addEvent(Event e)
   {
      if (!checkConflict(e))
      {
         events.add(e);
         return true;
      }

      return false;
   }

   /**
      Remove old event and replace it with a replacement.
      If edited event is conflicting with existing event, the old event
      will be added back in.
      @param oldEvent old event you want to "edit"
      @param newEvent new event with new information
      @return true if new event is added, false if there's a conflict
    */
   public boolean editEvent(Event oldEvent, Event newEvent)
   {
      if (removeEvent(oldEvent))
      {
         if (addEvent(newEvent))
            return true;
         else
         {
            addEvent(oldEvent);
            return false;
         }
      }

      return false;
   }
   
   /**
    * Removes the event using the eventID with the new event
    * @param eventID The id of the event to replace.
    * @param newEvent The new event to replace with the old event.
    * @return True if replaced, false otherwise.
    */
   public boolean editEvent(int eventID, Event newEvent)
   {
      Event oldEvent = null;
      
      for (Event e : events)
         if (eventID == e.getEventID())
            oldEvent = e;
      
      if ( oldEvent != null)
         return editEvent(oldEvent, newEvent);

      return false;
   }

   /**
    * Removes an event from this calendar.
    * @param event The event to be removed.
    * @return True if removed, false otherwise.
    */
   public boolean removeEvent(Event event)
   {
      for (Event e : events)
         if (event.equals(e))
         {
            events.remove(e);
            return true;
         }

      return false;
   }
   
   /**
      Delete an event based on the eventID.
    * @param eventID The id of the event to remove.
    * @return True if removed, false otherwise.
    */
   public boolean removeEvent(int eventID)
   {
      for (Event e : events)
      {
         if (e.getEventID() == eventID)
         {
            events.remove(e);
            return true;
         }
      }

      return false;
   }

   /**
    * Checks for conflict. Returns true if conflict, false if no conflict.
    * @param e The event to check for conflict against the list of events.
    * @return True if conflict, False if no conflict
    */
   public boolean checkConflict(Event e)
   {
      if (events.size() < 1)
         return false;

      if (e.getType().equals("deadline"))
         return false;

      GregorianCalendar eventStart, eventEnd;
      // check every event for conflicts
      for (Event event : events)
      {
         eventStart = event.getStartTime();
         eventEnd = event.getEndTime();
         // if event is already in the list, it is probably being updated so
         // skip conflict check for it
         // if (event == e)
         // continue;
         if (event.getType().equals("deadline"))
            continue;

         //check if event is recurring, if it is "readjust" the event to
         //the current day of the new event, e
         if (event.isRecurring())
         {
            // get day of week for Event e
            GregorianCalendar eStart = e.getStartTime();
            int dow = eStart.get(Calendar.DAY_OF_WEEK);

            // get day of week boolean array from event
            boolean[] targetDow = event.getDaysOfWeek();

            // recreate new Events
            if (targetDow[dow - 1])
            {
               int year = eStart.get(Calendar.YEAR);
               int month = eStart.get(Calendar.MONTH);
               int day = eStart.get(Calendar.DATE);

               int hr = eventStart.get(Calendar.HOUR_OF_DAY);
               int min = eventStart.get(Calendar.MINUTE);

               eventStart = new GregorianCalendar(year, month, day, hr, min);

               hr = eventEnd.get(Calendar.HOUR_OF_DAY);
               min = eventEnd.get(Calendar.MINUTE);

               eventEnd = new GregorianCalendar(year, month, day, hr, min);
            }
         }

         // compare start times
         int result = eventStart.compareTo(e.getStartTime());

         System.out.println("This event comes before/after: " + result);

         // if event is before the other event
         if (result < 0)
         {
            // check for overlap, if event's end time is later than other
            // event's start time, then overlap
            if (eventEnd.compareTo(e.getStartTime()) > 0)
            {
               System.out
                     .println("Before: event's endtime overlaps (after) with target starttime.");
               return true;
            }
         }
         // if event is later
         else if (result > 0)
         {
            // overlap if event's start date is earlier than other event's
            // end date then overlap
            if (eventStart.compareTo(e.getEndTime()) < 0)
            {
               System.out
                     .println("After: event's starttime overlaps with target endtime.");
               return true;
            }
         }
         // if event has same start time, they should overlap
         else
            return true;

      }

      // if all events searched then should be true
      return false;
   }//checkConflict

   /**
      Retrive an ArrayList of all valid event in a single week of a 
      corresponding date (year/month/day).
      @param year year of the date
      @param month month of the date
      @param day day of the date
      @return array list of all event of the week
    */
   public ArrayList<Event> getWeekEvents(int year, int month, int day)
   {
      ArrayList<Event> weekEvents = new ArrayList<Event>();

      // get year and week info
      GregorianCalendar date = new GregorianCalendar(year, month, day);
      int weekOfYear = date.get(Calendar.WEEK_OF_YEAR);

      for (Event e : events)
      {
         int eYear = e.getStartTime().get(Calendar.YEAR);

         if (year == eYear)
         {
            int eWOY = e.getStartTime().get(Calendar.WEEK_OF_YEAR);

            if (weekOfYear == eWOY || (weekOfYear > eWOY && e.isRecurring()))
               weekEvents.add(e);
         }
      }

      return weekEvents;
   }
   
   /**
    * Gets the week of around the given date.
    * @param date The date whose week to get events for.
    * @return The week of events of that date.
    */
   public ArrayList<Event> getWeekEvents(GregorianCalendar date)
   {
      ArrayList<Event> weekEvents = new ArrayList<Event>();

      // get year and week info
      int year = date.get(Calendar.YEAR);
      int weekOfYear = date.get(Calendar.WEEK_OF_YEAR);
      
      //beginning of the week
      GregorianCalendar weekBegin = new GregorianCalendar();
      weekBegin.set(Calendar.YEAR, year);
      weekBegin.set(Calendar.WEEK_OF_YEAR, weekOfYear);
      weekBegin.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
      weekBegin.set(Calendar.HOUR_OF_DAY, 0);
      weekBegin.set(Calendar.MINUTE, 0);
      
      //end of the week
      GregorianCalendar weekEnd = new GregorianCalendar();
      weekEnd.set(Calendar.YEAR, year);
      weekEnd.set(Calendar.WEEK_OF_YEAR, weekOfYear);
      weekEnd.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
      weekEnd.set(Calendar.HOUR_OF_DAY, 23);
      weekEnd.set(Calendar.MINUTE, 59);

      for (Event e : events)
      {
    	 GregorianCalendar eStart = e.getStartTime();
         int eYear = eStart.get(Calendar.YEAR);
         int eWOY = eStart.get(Calendar.WEEK_OF_YEAR);
         
         
         if(e.isRecurring())
         {
            boolean[] dow = e.getDaysOfWeek();
            GregorianCalendar eEnd = e.getEndTime();
            
            String eventName = e.getEventName();
            String type = e.getType();
            String descr = e.getDescription();
            String userName = e.getUserName();
            int eventid = e.getEventID();
            int groupid = e.getGroupID();
            
            if(eStart.compareTo(weekEnd) < 0)
            {
               int startDay = -1;
               
               //build list if recurring completely before current week
               if(eStart.compareTo(weekBegin) < 0)
                  startDay = 1;
               //build list if recur starts on this week
               else if(eStart.compareTo(weekEnd) < 0)
                  startDay = eStart.get(Calendar.DAY_OF_WEEK);
               
               for(int i = startDay; i < dow.length; i++)
               {
                  if(dow[i])
                  {
                     GregorianCalendar newStart = (GregorianCalendar) eStart.clone();
                     newStart.set(Calendar.YEAR, year);
                     newStart.set(Calendar.WEEK_OF_YEAR, weekOfYear);
                     newStart.set(Calendar.DAY_OF_WEEK, i);
                     
                     GregorianCalendar newEnd = (GregorianCalendar) eEnd.clone();
                     newEnd.set(Calendar.YEAR, year);
                     newEnd.set(Calendar.WEEK_OF_YEAR, weekOfYear);
                     newEnd.set(Calendar.DAY_OF_WEEK, i);
                     
                     Event newEv = new Event(eventid, eventName, groupid, userName,
                                             type, newStart, newEnd, descr);
                     newEv.setRecurrence(dow, "Weekly");
                     
                     weekEvents.add(newEv);
                  }
               }
            }
            //do nothing if recur after the current week
            
         }
         else if(year == eYear && weekOfYear == eWOY)
            weekEvents.add(e);
      
      }

      return weekEvents;
   }
   
   /**
    * Prints out all the events of the calendar.
    */
   public String toString()
   {
      String s = "";
      for(Event e : events)
         s += e + "\n";
      return s;
   }
   
   /**
    * Adds event without checking conflicts.
    * @param event The event to add to the calendar.
    */
   public void overloadEvents(Event event)
   {
      events.add(event);
   }
   
   /**
    * Adds a list of events to the calendar without checking conflicts
    * @param eventList The list of events to add.
    */
   public void overloadEvents(ArrayList<Event> eventList)
   {
      for(Event e: eventList)
         events.add(e);
   }

   /**
    * Gets the list of events of this calendar.
    * @return The list of events of this calendar.
    */
   public ArrayList<Event> getEvents()
   {
      return events;
   }

   /**
    * Gets the number of events in this calendar.
    * @return The number of events in this calendar.
    */
   public int getNoOfEvents()
   {
      return events.size();
   }

}
